/**
 * 合成事件对象
 * react中的SyntheticEvent 包含了很多属性和方法
 * 这里只实现一个阻止冒泡
 * 1. 创建一个和原生DOM同名的阻止冒泡方法
 * 2. 当开发者调用stopPropagation方法时，将_stopPropagation设置为true
 * 3. 调用原生事件对象的stopPropagation方法阻止冒泡
 */
class SyntheticEvent {
  constructor(e) {
    // 保存原生事件对象
    this.nativeEvent = e;
  }
  // 合成事件对象要提供一个和原生DOM同名的阻止冒泡方法
  stopPropagation() {
    // 当开发者调用stopPropagation方法时，将_stopPropagation设置为true
    this._stopPropagation = true;
    if (this.nativeEvent.stopPropagation) {
      // 调用原生事件对象的stopPropagation方法阻止冒泡
      this.nativeEvent.stopPropagation();
    }
  }
}

/**
 * 整个流程大致为：
 * 1. 通过addEvent方法给根元素绑定事件， 使用我们自己的事件委托
 * 2. addEvent中调用dispatchEvent方法进行事件的派发
 *   2.1 实例化一个合成事件对象SyntheticEvent
 *   2.2 找到对应的fiberNode
 *   2.3 收集FiberNode一直向上到HostRootFiberNode路径中所有type类型的事件回调函数`FiberNode.memoizedProps`属性所保存的对应的事件处理函数
 *   2.4 模拟捕获的实现
 *       目标元素向上遍历，因此收集的顺序是【目标元素的事件回调，某个祖先元素的事件回调，更上层祖先元素的事件回调】，因此要从后往前遍历 并 执行回调函数
 *   2.5 模拟冒泡的实现
 *       目标元素向下遍历，因此收集的顺序是【目标元素的事件回调，某个祖先元素的事件回调，更上层祖先元素的事件回调】，因此要从前往后遍历 并 执行回调函数
 */

/**
 * 用于给根元素绑定事件
 * @param {*} container 根元素
 * @param {*} type 事件类型
 */
export const addEvent = (container, type) => {
  container.addEventListener(type, (e) => {
    dispatchEvent(e, type.toUpperCase()); // 进行事件的派发
  });
};

/**
 * 该方法用于收集路径中所有type类型的事件回调函数
 * @param {*} type 事件类型
 * @param {*} begin 开始查找的FiberNode
 * @returns [{type: function(){}}]
 */
const collectPaths = (type, begin) => {
  const paths = []; // 存放收集到的所有回调函数
  // 如果不是HostRootFiber,则一直向上查找
  while (begin.tag !== 3) {
    const { memoizedProps, tag } = begin;
    if (tag === 5) {
      // DOM 元素对应的FiberNode
      const eventName = "bind" + type; // bindCLICK
      // 判断当前节点是否有绑定事件
      if (memoizedProps && Object.keys(memoizedProps).includes(eventName)) {
        // 当前节点绑定了事件，将回调函数收集起来
        const pathNode = {};
        pathNode[type] = memoizedProps[eventName];
        paths.push(pathNode);
      }
      begin = begin.return; // 向上查找
    }
  }
  return paths;
};
/**
 *
 * @param {*} path 收集到的事件回调函数数组
 * @param {*} type 事件类型
 * @param {*} syntheticEvent 合成事件对象
 */
const triggerEventFlow = (path, type, syntheticEvent) => {
  // 遍历收集到的事件回调函数数组，执行回调函数即可
  // 模拟捕获阶段的实现，需要从后往前遍历数组并执行回调
  for (let i = paths.length; i--; ) {
    const pathNode = paths[i];
    const callback = pathNode[type]; // 拿到回调函数
    if (callback) {
      // 存在回调函数，执行该回调
      callback.call(null, syntheticEvent);
    }
    if (syntheticEvent._stopPropagation) {
      // 说明在当前的事件回调函数中，开发者阻止了继续往上的冒泡
      break; // 直接跳出循环即可
    }
  }
};

/**
 * 事件的派发
 * @param {*} e  原生事件对象
 * @param {*} type 事件类型（UpperCase click->CLICK）
 */
const dispatchEvent = (e, type) => {
  // 实例化合成事件对象
  const syntheticEvent = new SyntheticEvent(e);
  // 拿到触发事件的元素
  const target = e.target;
  let fiber;
  // 通过DOM元素找到对应的FiberNode
  for (let prop in target) {
    if (prop.toLocaleLowerCase().includes("fiber")) {
      fiber = target[prop]; // 找到对应的FiberNode
    }
  }
  // 找到对应的FiberNode之后，从FiberNode开始向上查找，直到根节点 搜集路径中该事件类型所对应的所有回调函数
  const paths = collectPaths(type, fiber);
  // 模拟捕获的实现
  triggerEventFlow(paths, type + "CAPTURE", syntheticEvent);
  // 模拟冒泡的实现
  if (!syntheticEvent._stopPropagation) {
    // 如果没有阻止冒泡，只需要将path进行反向，再遍历执行一次即可
    triggerEventFlow(paths.reverse(), type, syntheticEvent);
  }
};
